package main;
import handlers.SkillHandler;
import influence_radii.Angular;
import influence_radii.InfluenceRadius;
import influence_radii.Linear;
import influence_radii.MeteorShower;
import influence_radii.Radial;
import item.Mount;
import item.Obstacle;
import item.OneHanded;
import item.TakeableItem;
import item.Trap;
import item.TwoHanded;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import loadsave.Parser;
import loadsave.loadsaveobject.Iterator;
import loadsave.loadsaveobject.LoadSaveObject;
import map.Decal;
import map.Direction;
import map.GameMap;
import map.GameWorld;
import map.MapBuilder;
import map.Position;
import map.areaeffect.AreaEffect;
import occupation.Occupation;
import skills.ActiveSkill;
import stat.NonEntityStatList;
import stat.StatList;
import stat.StatManager;
import stat.StatType;
import view.GameView;
import view.GameWindow;
import view.drawing.DrawManager;
import view.drawing.SingleFrameSpriteSheet;
import view.drawing.SpriteSheet;
import view.drawing.SpriteSheetSet;
import view.overlay.AssignableControlsOverlay;
import view.overlay.InteractionOverlay;
import view.overlay.InventoryOverlay;
import view.overlay.MapOverlay;
import view.overlay.MenuOverlay;
import view.overlay.ResetOverlay;
import view.overlay.ScrollingTextOverlay;
import view.overlay.ShopkeepingOverlay;
import view.overlay.SkillOverlay;
import view.overlay.SkillPointAssignmentOverlay;
import view.overlay.StatsOverlay;
import ai.PetAI;
import ai.WorldAI;
import controller.AssignableControlController;
import controller.AudioController;
import controller.ControlConfiguration;
import controller.Controller;
import controller.CreditsController;
import controller.DeathController;
import controller.EntityController;
import controller.FullScreenController;
import controller.GameController;
import controller.IntroController;
import controller.InventoryController;
import controller.MainMenuController;
import controller.MapController;
import controller.NPCInteractionController;
import controller.ResetController;
import controller.ShopkeepingController;
import controller.SkillPointAssignmentController;
import controller.SkillsController;
import controller.StatsController;
import effects.DamageEffect;
import effects.Effect;
import effects.GainExperienceEffect;
import effects.HealEffect;
import effects.InstantDeathEffect;
import effects.RiverEffect;
import effects.TeleportEffect;
import entity.Avatar;
import entity.BattleNPC;
import entity.Pet;
import entity.WorldNPC;
import factory.OccupationInitializer;
import factory.SkillFactory;
import factory.SkillInitializer;

public class RunGame {
	
	List<Controller> mControllers;
	
	GameWindow window;
	GameView gameView;
	
	MapOverlay mapOverlay;
	StatsOverlay statsOverlay;
	
	ControlConfiguration config;
	FullScreenController fsc;
	EntityController ec;
	
	// Matt controllers
	IntroController introController;
	MainMenuController mainMenuController;
	GameController gameController;
	AssignableControlController controlsController;
	CreditsController creditsController;
	InventoryController inventoryController;
	StatsController statsController;
	SkillPointAssignmentController skillPointAssignmentController;
	SkillsController skillsController;
	NPCInteractionController interactionController;
	AudioController audioController;
	ShopkeepingController shopkeepingController;
	ResetController resetController;
	DeathController deathController;
	
	
	// Matt overlays
	ShopkeepingOverlay shopkeepingOverlay;
	AssignableControlsOverlay assignableControlsOverlay;
	InventoryOverlay inventoryOverlay;
	SkillPointAssignmentOverlay skillPointAssignmentOverlay;
	SkillOverlay skillOverlay;
	InteractionOverlay interactionOverlay;
	ResetOverlay resetOverlay;
	MenuOverlay creatorOverlay;
	
	MapController mapController;
	
	MenuOverlay menuOverlay;
	ScrollingTextOverlay scrollerOverlay;
	ScrollingTextOverlay creditsOverlay;
	
	public RunGame() {
		
		// Initialize the window system
		window = new GameWindow();
		gameView = new GameView();
		
		
		load();
		start();
	}
	
	public void changeOccupation(Occupation occ) {
		GameWorld world = GameWorld.getInstance();
		
		Avatar a = world.getAvatar();
		
		a.setOccupation(occ);
		
		if (mapOverlay != null) {
			skillsController.occupationChanged(occ);
			skillsController.setHandler(new SkillHandler(a));
			
		}
	}

	
	public static void main(String args[]) {
		
		GameWorld world = GameWorld.getInstance();
		Avatar avatar = new Avatar("minotaur", new Occupation());
		avatar.setPosition(new Position(10, 10));
		avatar.setDirection(Direction.NORTH);
		world.setAvatar(avatar);
		
		// Initializing SkillFactory
		SkillInitializer.initSkillFactory();
		
		// Adding skills to avatar occupation
		avatar.getOccupation().addActiveSkill( SkillFactory.createActiveSkill( "fireBall" ) );
		avatar.getOccupation().addActiveSkill( SkillFactory.createActiveSkill( "iceWave" ) );
		avatar.getOccupation().addActiveSkill( SkillFactory.createActiveSkill( "earthQuake" ) );
		avatar.getOccupation().addUpgradableSkill( SkillFactory.createSkill( "detectTrap" ) );
		avatar.getOccupation().addUpgradableSkill( SkillFactory.createSkill( "removeTrap" ) );
		avatar.getOccupation().addUpgradableSkill( SkillFactory.createSkill( "pickPocket" ) );
		
		// Create entities;
		WorldNPC enemy1 = new WorldNPC("theseus",new Occupation());
		enemy1.getInventory().addItem(new OneHanded("blah"));
		enemy1.getInventory().addItem(new TwoHanded("blah3"));
		enemy1.getInteractionOptions().setMessage("Your code is fundamentally flawed...");
		enemy1.setPosition(new Position(15, 14));
		
		SkillInitializer.initSkillFactory();
		OccupationInitializer.initOccupationFactory();
		
		// Creating battle npcs
		BattleNPC battle1 = new BattleNPC("battle1", new Occupation());
		BattleNPC battle2 = new BattleNPC("battle2", new Occupation());
		battle1.setPosition(new Position(2,5));
		battle2.setPosition(new Position(7,5));
		
		// Creating ActiveSkills
		StatList st1 = new NonEntityStatList();
		StatList st2 = new NonEntityStatList();
		st1.update(StatType.HP, 20);
		st2.update(StatType.HP, 30);
		Effect effect1 = new DamageEffect("effect1",st1);
		Effect effect2 = new DamageEffect("effect2",st2);
		effect1.addRelevantStat(StatType.HP);
		effect1.addOutputStat(StatType.HP);
		effect2.addRelevantStat(StatType.HP);
		effect2.addOutputStat(StatType.HP);
		InfluenceRadius r1 = new Radial();
		InfluenceRadius l1 = new Linear();
		InfluenceRadius a1 = new Angular();
		InfluenceRadius m1 = new MeteorShower();
		
		ActiveSkill as1 = new ActiveSkill("as1",1, effect1, r1, 1, 3, 2, 1, 15);
		ActiveSkill as2 = new ActiveSkill("as2",1, effect1, l1, 1, 4, 2, 1, 15);
		ActiveSkill as3 = new ActiveSkill("as3",1, effect1, a1, 1, 2, 3, 1, 15);
		ActiveSkill as4 = new ActiveSkill("as4",1, effect1, m1, 1, 5, 4, 1, 15);
		
		/*
		battle1.getOccupation().addActiveSkill(as1);
		battle1.getOccupation().addActiveSkill(as2);
		battle1.getOccupation().addActiveSkill(as3);
		battle1.getOccupation().addActiveSkill(as4);
		*/
		
		battle1.getOccupation().addActiveSkill(SkillFactory.createActiveSkill( "fireBall" ));
		battle1.getStats().update(StatType.LIVES, -4);
		battle1.getStats().update(StatType.HP, 30);
		battle2.getOccupation().addActiveSkill(SkillFactory.createActiveSkill( "iceWave" ));
		battle2.getStats().update(StatType.LIVES, -4);
		battle2.getStats().update(StatType.HP, 10);
		/*
		battle2.getOccupation().addActiveSkill(as1);
		battle2.getOccupation().addActiveSkill(as2);
		battle2.getOccupation().addActiveSkill(as3);
		battle2.getOccupation().addActiveSkill(as4);
		*/
		
		// Adding battle npcs to worldnpc
		enemy1.addSpawningNPC(battle1);
		enemy1.addSpawningNPC(battle2);
		
		// Create Pet
		Pet pet = new Pet("pet",new Occupation());
		pet.setPosition(new Position(6, 11));		
		
		@SuppressWarnings("unused")
		RunGame rg = new RunGame();
		
		//Create some sample maps and add teleporters between them
		GameMap map1 = MapBuilder.getInstance().buildMap("res/other/map1.txt");
		map1.setSpawn(new Position(10, 10));
		world.addMap(map1);
		
		TeleportEffect te1 = new TeleportEffect("t1", "map2");
		AreaEffect ae1 = new AreaEffect(te1, 40, "ae1");
		map1.addAreaEffect(ae1, new Position(10, 1));
		
		Obstacle thatsNoMoon = new Obstacle("nomoon");
		map1.addItem(thatsNoMoon, new Position(10, 2));
		
		Decal tele = new Decal("portal");
		map1.addDecal(tele, new Position(10, 1));
		
		Decal blood = new Decal("blood");
		map1.addDecal(blood, new Position(10, 10));
		
		NonEntityStatList desl = new NonEntityStatList();
		desl.update(StatType.STR, 20);
		DamageEffect de = new DamageEffect("de", desl);
		de.addRelevantStat(StatType.STR);
		de.addOutputStat(StatType.HP);
		AreaEffect ae3 = new AreaEffect(de, 40, "ae3");
		map1.addAreaEffect(ae3, new Position(11, 4));
		
		Decal bio = new Decal("biohazard");
		map1.addDecal(bio, new Position(11, 4));
		
		NonEntityStatList hesl = new NonEntityStatList();
		hesl.update(StatType.STR, 20);
		HealEffect he = new HealEffect("he", hesl);
		he.addRelevantStat(StatType.STR);
		he.addOutputStat(StatType.HP, StatType.MP);
		AreaEffect ae4 = new AreaEffect(he, 40, "ae4");
		map1.addAreaEffect(ae4, new Position(12, 4));
		
		Decal redX = new Decal("redx");
		map1.addDecal(redX, new Position(12, 4));
		
		NonEntityStatList idsl = new NonEntityStatList();
		idsl.update(StatType.LIVES, 1);
		InstantDeathEffect ie = new InstantDeathEffect("ie", idsl);
		ie.addRelevantStat(StatType.LIVES);
		AreaEffect ae6 = new AreaEffect(ie, 40, "ae6");
		map1.addAreaEffect(ae6, new Position(11, 5));
		
		Decal skull = new Decal("skull");
		map1.addDecal(skull, new Position(11, 5));
		
		NonEntityStatList gesl = new NonEntityStatList();
		gesl.update(StatType.EXP, 30);
		GainExperienceEffect ge = new GainExperienceEffect("ge", gesl);
		ge.addRelevantStat(StatType.EXP);
		AreaEffect ae5 = new AreaEffect(ge, 10, "ae5");
		map1.addAreaEffect(ae5, new Position(10, 5));
		
		Decal goldStar = new Decal("goldstar");
		map1.addDecal(goldStar, new Position(10, 5));
		
		RiverEffect re1 = new RiverEffect("river1", 10, Direction.NORTH);
		AreaEffect riverEffect1 = new AreaEffect(re1, re1.getForce(), "riverEffect1");
		map1.addAreaEffect(riverEffect1, new Position(16, 9));
		
		Decal arrowN = new Decal("arrow_n");
		map1.addDecal(arrowN, new Position(16, 9));
		
		RiverEffect re2 = new RiverEffect("river2", 10, Direction.NORTH);
		AreaEffect riverEffect2 = new AreaEffect(re2, re2.getForce(), "riverEffect2");
		map1.addAreaEffect(riverEffect2, new Position(16, 8));
		
		map1.addDecal(arrowN, new Position(16, 8));
		
		RiverEffect re3 = new RiverEffect("river3", 10, Direction.NORTH);
		AreaEffect riverEffect3 = new AreaEffect(re3, re3.getForce(), "riverEffect3");
		map1.addAreaEffect(riverEffect3, new Position(16, 7));
		
		map1.addDecal(arrowN, new Position(16, 7));
		
		Decal arrowNE = new Decal("arrow_ne");
		map1.addDecal(arrowNE, new Position(16, 6));
		
		RiverEffect re4 = new RiverEffect("river4", 10, Direction.NORTHEAST);
		AreaEffect riverEffect4 = new AreaEffect(re4, re4.getForce(), "riverEffect4");
		map1.addAreaEffect(riverEffect4, new Position(16, 6));
		
		GameMap mapCombat = MapBuilder.getInstance().buildMap("res/other/map_combat.txt");
		mapCombat.setSpawn(new Position(5, 6));
		world.addMap(mapCombat);
		
		TeleportEffect te3 = new TeleportEffect("t3", "map1");
		AreaEffect ae7 = new AreaEffect(te3, 40, "ae7");
		mapCombat.addAreaEffect(ae7, new Position(5, 1));
		mapCombat.addDecal(tele, new Position(5, 1));
		
		GameMap map2 = MapBuilder.getInstance().buildMap("res/other/map2.txt");
		map2.setSpawn(new Position(10, 10));
		world.addMap(map2);
		
		GameMap map3 = MapBuilder.getInstance().buildMap("res/other/map3.txt");
		map3.setSpawn(new Position(10, 16));
		world.addMap(map3);
		
		AreaEffect teleTo1 = new AreaEffect(te3, 40, "teleTo1");
		AreaEffect teleTo2 = new AreaEffect(te1, 40, "teleTo2");
		map3.addAreaEffect(teleTo1, new Position(9, 17));
		map3.addAreaEffect(teleTo2, new Position(11, 16));
		map3.addDecal(tele, new Position(9, 17));
		map3.addDecal(tele, new Position(11, 16));
		
		TeleportEffect te2 = new TeleportEffect("t2", "map3");
		AreaEffect ae2 = new AreaEffect(te2, 40, "ae2");
		map2.addAreaEffect(ae2, new Position(10, 1));
		map2.addDecal(tele, new Position(10, 1));
		
		Trap trap = new Trap("trollface", effect1);
		//map1.addItem(trap, new Position(3, 9));
		map1.addTrap(trap, new Position(3, 9));
		trap.setVisible(false);
		//Add npc's to map
		map2.addEntity(enemy1, enemy1.getPosition());
		map2.addEntity(pet, pet.getPosition());
		
		// Add a mount
		TakeableItem mt = new Mount("pony", "blarg");
//		map2.addItem(mt, new Position(6, 3));
		avatar.getInventory().addItem(mt);
		
//
//		//Artificial Intelligence
		WorldAI wai = new WorldAI();
		PetAI pai = new PetAI(avatar);
		
		map2.addAI(wai, enemy1);
		map2.addAI(pai, pet);
		
		//These things have to be done last!!!
		world.setCurrentMap(map1);
		world.getCurrentMap().addEntity(avatar, new Position(10, 10));
		
		

	}
	
	public void restart() {
		
		// reset the controllers
//		for(Controller c : mControllers) {
//			c.reset();
//		}
		
		load();
		
		start();
	}
	
	private void load() {
		mControllers = new ArrayList<Controller>();
		
		// Load the drawing manager before anything
		loadDrawManager("data/entity.oml");
		loadDrawManager("data/items.oml");
		loadDrawManager("data/terrain.oml");
		loadDrawManager("data/decals.oml");
		loadDrawManager("data/skills.oml");
		
		SkillInitializer.initSkillFactory();
		OccupationInitializer.initOccupationFactory();
		
		System.out.format("RunGame::RunGame() > DrawManager loaded successfully...");
		

		
		config = new ControlConfiguration();
		
		Avatar avatar = GameWorld.getInstance().getAvatar();
		Position center = new Position(avatar.getPosition().getX(), avatar.getPosition().getY());
		
		// Create a stat manager for the Skill Point assigner
		StatManager statManager = new StatManager(avatar.getStats());
		
		mapOverlay = new MapOverlay();
		mapOverlay.setCenter(center);
		statsOverlay = new StatsOverlay();
		
		
		// === Overlays ===
		shopkeepingOverlay = new ShopkeepingOverlay();
		interactionOverlay = new InteractionOverlay();
		skillOverlay = new SkillOverlay();
		menuOverlay = new MenuOverlay();
		creditsOverlay = new ScrollingTextOverlay();
		scrollerOverlay = new ScrollingTextOverlay(new File("res/other/story2.txt"));
		scrollerOverlay.setBackgroundImage(new File("res/images/backgrounds/minotaur_intro.png"));
		assignableControlsOverlay = new AssignableControlsOverlay();
		inventoryOverlay = new InventoryOverlay();
		skillPointAssignmentOverlay = new SkillPointAssignmentOverlay(statManager);
		resetOverlay = new ResetOverlay();
		creatorOverlay = new MenuOverlay();
		
		
	
		
		// === Add Overlays to GameView ===
		gameView.addOverlay(mapOverlay);
		gameView.addOverlay(skillOverlay);
		gameView.addOverlay(statsOverlay);
		gameView.addOverlay(inventoryOverlay);
		gameView.addOverlay(skillPointAssignmentOverlay);
		gameView.addOverlay(skillOverlay);
		gameView.addOverlay(interactionOverlay);
		gameView.addOverlay(shopkeepingOverlay);
		gameView.addOverlay(menuOverlay);
		gameView.addOverlay(creatorOverlay);
		gameView.addOverlay(scrollerOverlay);
		gameView.addOverlay(assignableControlsOverlay); // Connaiscence of position, This must be added AFTER menuOverlay
		gameView.addOverlay(creditsOverlay);
		gameView.addOverlay(resetOverlay);
		
		// Add death controller
		deathController = new DeathController();
		deathController.setPlayer(avatar);
		
		
		
		// === Controllers ===
		resetController = new ResetController(this, resetOverlay);
		shopkeepingController = new ShopkeepingController(shopkeepingOverlay);
		audioController = new AudioController();
		skillsController = new SkillsController(avatar.getOccupation(), new SkillHandler(avatar), skillOverlay);
		skillPointAssignmentController = new SkillPointAssignmentController(skillPointAssignmentOverlay, new StatManager(avatar.getStats()));
		inventoryController = new InventoryController(avatar, inventoryOverlay);
		mapController = new MapController(mapOverlay);
		creditsController = new CreditsController(creditsOverlay);
		statsController = new StatsController(statsOverlay);
		gameController = new GameController(mapOverlay, inventoryController, statsController, skillPointAssignmentController, skillsController);
		controlsController = new AssignableControlController(config, assignableControlsOverlay);
		mainMenuController = new MainMenuController(this, menuOverlay, creatorOverlay, controlsController, gameController);
		introController = new IntroController(scrollerOverlay, mainMenuController);
		introController.setAudioFile(new File("res/audio/intro.mid"));
		fsc = new FullScreenController(window);
		ec = new EntityController(GameWorld.getInstance().getAvatar());
		interactionController = new NPCInteractionController(interactionOverlay);
		
		
		// Add the controllers for later reset
		mControllers.add(resetController);
		mControllers.add(shopkeepingController);
		mControllers.add(audioController);
		mControllers.add(skillsController);
		mControllers.add(skillPointAssignmentController);
		mControllers.add(inventoryController);
		mControllers.add(mapController);
		mControllers.add(creditsController);
		mControllers.add(statsController);
		mControllers.add(gameController);
		mControllers.add(controlsController);
		mControllers.add(mainMenuController);
		mControllers.add(introController);
		mControllers.add(fsc);
		mControllers.add(ec);
		mControllers.add(interactionController);
		
		// === Control Configuration ===
		config.registerControlList(resetController.getKeyControls());
		config.registerControlList(shopkeepingController.getKeyControls());
		config.registerControlList(interactionController.getKeyControls());
		config.registerControlList(skillsController.getKeyControls());
		config.registerControlList(skillPointAssignmentController.getKeyControls());
		config.registerControlList(creditsController.getKeyControls());
		config.registerControlList(mapController.getKeyControls());
		config.registerControlList(controlsController.getKeyControls());
		config.registerControlList(fsc.getKeyControls());
		config.registerControlList(ec.getKeyControls());
		config.registerControlList(introController.getKeyControls());
		config.registerControlList(inventoryController.getKeyControls());


		// === Game Window ===
		window.setGameView(gameView);
		gameView.requestFocusInWindow();
		gameView.addKeyListener(config);
		
		
		// Make the window visible
		window.setVisible(true);
	}
	
	private void start() {
		
		// Start the intro sequence
		introController.start();
	}
	
	private void loadDrawManager(String filename) {
		DrawManager dm = DrawManager.getInstance();
		
		try {
			
		
			// Parse entity
			LoadSaveObject lso = Parser.parse(filename);
			
			LoadSaveObject lsoDM = lso.get("manager");
			
			LoadSaveObject lsoModelSheets = lsoDM.get("mModelSheets");
			
			Iterator<LoadSaveObject> it = lsoModelSheets.getIterator();
			
			for (it.init(); it.hasNext(); it.next()) {
				
				
				String name = it.getCurrKey().getValue();
				SpriteSheetSet set = new SpriteSheetSet();
				
				// Load the set
				
				LoadSaveObject setObject = it.getCurrVal();
				LoadSaveObject sheetsObject = setObject.get("mSheets");
				Iterator<LoadSaveObject> itSet = sheetsObject.getIterator();
				
				for (itSet.init(); itSet.hasNext(); itSet.next()) {
					String setStateName = itSet.getCurrKey().getValue();
					
					// Load the sprite sheet
					LoadSaveObject sheetObj = itSet.getCurrVal();
					String filePath = sheetObj.get("mImagePath").getValue();
					
					SpriteSheet sheet = new SingleFrameSpriteSheet(filePath);
					set.addSpriteSheet(setStateName, sheet);
					
					System.out.format("Loaded SpriteSheet: {name=%s state=%s path=%s}\n", name, setStateName, filePath);
				}
				
				
				dm.addSpriteSheetSet(name, set);
				
				
			}
		}
		catch (Exception ex) {
			System.out.println(ex.toString());
		}
	}
}
